package com.smap.event;

import java.util.Date;

import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.event.dom.client.HumanInputEvent;
import com.google.gwt.event.dom.client.MouseEvent;
import com.google.gwt.event.dom.client.MouseWheelEvent;
import com.google.gwt.event.dom.client.TouchEvent;
import com.google.gwt.event.shared.GwtEvent;

@SuppressWarnings("rawtypes")
public class MapEvent extends GwtEvent<MapHandler> {
	public static String EVT_TILE_FETCHED = "EVT_TILE_FETCHED";
	public static String EVT_MAP_DRAWN = "EVT_MAP_DRAWN";
	public static String EVT_FEAT_SELECTED = "EVT_FEAT_SELECTED";
	public static String EVT_FEAT_ADDED = "EVT_FEAT_ADDED";

	public static String ALLOW_EVENTS = "mousewheel,dragstart,draging,dragend,click,dbclick";

	public static MapEvent convert(HumanInputEvent evt) {
		MapEvent mEvent = new MapEvent();
		if (evt instanceof TouchEvent) {
			mEvent.read((TouchEvent) evt);
		}
		if (evt instanceof MouseEvent) {
			mEvent.read((MouseEvent) evt);
		}

		timestamp = new Date().getTime();
		if (filter(mEvent)) {
			return mEvent;
		} else {
			return null;
		}
	}

	static private boolean filter(MapEvent evt) {
		return (ALLOW_EVENTS.contains(evt.type));
	}

	public String type;
	public int x;
	public int y;
	public int moveX;
	public int moveY;
	public int deltaY;
	public int btn;
	public Object data;
	public boolean stop = false;

	public static int START_X;
	public static int START_Y;
	static boolean MOUSE_HOLD = false;
	static boolean DRAGING = false;
	static int lastX;
	static int lastY;

	static long timestamp;

	private boolean isDbclick(int x, int y, boolean isTouch) {
		long now = new Date().getTime();
		if (isTouch) {
			if (now - timestamp < 500) {
				return true;
			}
		} else {
			if ((now - timestamp < 500)
					&& (Math.abs(lastX - x) + Math.abs(lastY - y) < 5)) {
				return true;
			}
		}
		return false;
	}

	private void setLast(int x, int y) {
		lastX = x;
		lastY = y;
	}

	public static Type<MapHandler> TYPE = new Type<MapHandler>();

	public MapEvent() {
	}

	public void stop() {
		this.stop = true;
	}

	public MapEvent(String type, Object data) {
		this.type = type;
		this.data = data;
	}

	protected void read(TouchEvent evt) {
		String type = evt.getAssociatedType().getName();
		this.type = type;
		if (evt.getTouches() != null && evt.getTouches().length() > 0) {
			Touch touch = evt.getTouches().get(0).cast();
			this.x = touch.pageX();
			this.y = touch.pageY();
		}

		if ("touchstart".equals(type)) {
			MOUSE_HOLD = true;
			DRAGING = false;
			START_X = this.x;
			START_Y = this.y;
			if (isDbclick(this.x, this.y, true)) {
				this.type = "dbclick";
				setLast(0, 0);
			} else {
				setLast(this.x, this.y);
			}
		} else if ("touchmove".equals(type)) {
			if (MOUSE_HOLD) {
				this.moveX = this.x - START_X;
				this.moveY = this.y - START_Y;
				if (!DRAGING) {
					int moved = Math.abs(this.moveX) + Math.abs(this.moveY);
					if (moved > 4) {
						DRAGING = true;
						this.type = "dragstart";
					}
				} else {
					this.type = "draging";
				}
			}
		} else {
			if ("touchend".equals(type)) {
				if (isDbclick(this.x, this.y, true)) {
					this.type = "click";
					this.x = lastX;
					this.y = lastY;
				}
			}
			if (DRAGING) {
				this.type = "dragend";
			}
			MOUSE_HOLD = false;
			DRAGING = false;
		}
	}

	private long getTime() {
		return new Date().getTime();
	}

	protected void read(MouseEvent evt) {
		if (evt.isShiftKeyDown()) {
			this.btn = 1;
		} else if (evt.isControlKeyDown()) {
			this.btn = 2;
		} else if (evt.isAltKeyDown()) {
			this.btn = 3;
		} else {
			this.btn = -1;
		}

		String type = evt.getAssociatedType().getName();
		this.type = type;

		this.x = evt.getX();
		this.y = evt.getY();
		if (evt instanceof MouseWheelEvent) {
			MouseWheelEvent e = (MouseWheelEvent) evt;
			deltaY = e.getDeltaY();
		}

		if ("mousedown".equals(type)) {
			MOUSE_HOLD = true;
			DRAGING = false;
			START_X = this.x;
			START_Y = this.y;
			if (isDbclick(this.x, this.y, false)) {
				this.type = "dbclick";
				setLast(0, 0);
			} else {
				setLast(this.x, this.y);
			}
		} else if ("mousemove".equals(type)) {
			if (MOUSE_HOLD) {
				this.moveX = this.x - START_X;
				this.moveY = this.y - START_Y;
				if (!DRAGING) {
					int moved = Math.abs(this.moveX) + Math.abs(this.moveY);
					if (moved > 8) {
						DRAGING = true;
						this.type = "dragstart";
					}
				} else {
					this.type = "draging";
				}
			}
		} else {
			if ("mouseup".equals(type)) {
				if (isDbclick(this.x, this.y, false)) {
					this.type = "click";
				}
			}
			if (DRAGING) {
				this.type = "dragend";
			}
			MOUSE_HOLD = false;
			DRAGING = false;
		}
	}

	@Override
	public Type<MapHandler> getAssociatedType() {
		return TYPE;
	}

	@Override
	protected void dispatch(MapHandler handler) {
		handler.onMapEvent(this);
	}

	@Override
	public String toString() {
		return type + "(" + x + "," + y + ")";
	}

}

class Touch extends JavaScriptObject {
	protected Touch() {
	}

	public final native int pageX() /*-{
		return this.pageX;
	}-*/;

	public final native int pageY() /*-{
		return this.pageY;
	}-*/;
}
